﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SimulationPresenter.Presentation.Primitives;
using SimulationPresenter.Simulation.Primitive;
using SimulationPresenter.Simulation.Enums;
using Microsoft.DirectX;
using SimulationPresenter.Presentation.Helpers;
using SimulationPresenter.Simulation;
using SimulationPresenter.Simulation.Helpers;
using Microsoft.DirectX.Direct3D;
using System.Data;
using SimulationPresenter.UI;

namespace SimulationPresenter.Theater.Actors
{
    internal class PoltergeistActor : Actor, IDrawable, ISimulatable, IActorPropertiesProvider, ISimulationTypeChangeableActor
    {
        private IPresentationPrimitive shape;
        private ISimulationPrimitive state;

        /// <summary>
        /// Creates actor based on shape. No point of having actor without shape
        /// </summary>
        /// <param name="shape"></param>
        /// <param name="name"></param>
        internal PoltergeistActor(IPresentationPrimitive shape, string name)
        {
            this.shape = shape;
            state = new DummySimulative 
                        { 
                            Name = name
                        };
        }

        /// <summary>
        /// Sets initial position of an actor.
        /// </summary>
        /// <param name="position"></param>
        internal void ToPosition(Vector3 position)
        {
            state.Transformation = state.Transformation * Matrix.Translation(position);
        }
        
        /// <summary>
        /// Sets type of simulation that is applied to actor.
        /// Currently supported are:
        ///     MetalCloth
        ///     RidigMesh
        /// </summary>
        /// <typeparam name="T">Type of simulation applied.</typeparam>
        public void As<T>(SimulationEnvironment environment) where T : ISimulationPrimitive
        {
            //TODO: Check if works as expected
            RemoveSimulationState(environment);
            if (typeof(T) == typeof(RidigMesh))
	        {
                state = new RidigMesh
                            {
                                BodyShape = shape,
                                Name = state.Name,
                                Transformation = state.Transformation 
                            };
	        }
            else if (typeof(T) == typeof(MetalCloth))
	        {
                state = new MetalCloth
                            {
                                Name = state.Name,
                                Transformation = Matrix.Identity,
                                ImpulseThreshold = MetalCloth.defaultImpulseThreshold * 2f,
                                PenetrationDepth = MetalCloth.defaultPenetrationDepth * 0.1f,
                                Grid = new IndexedVectorGrid
                                {
                                    Indices = shape.Indices.Select(x => (int)x).ToArray(),
                                    Vertexes = shape.GetPositionsWithScale().TransformByMatrix(state.Transformation)
                                }
                            };
                //Reset scale since applied above to state
                shape.UseScale = false;
	        }
            else if (typeof(T) == typeof(DummySimulative))
            {
                state = new DummySimulative
                            {
                                Name = state.Name,
                                Transformation = state.Transformation
                            };
            }
            else
            {
                throw new NotSupportedException("Only Ridig and Cloth bodies are supported");
            }
        }

        /// <summary>
        /// Removes old simulation state from simulation environment (if applicable)
        /// </summary>
        /// <param name="environment"></param>
        private void RemoveSimulationState(SimulationEnvironment environment)
        {
            try
            {
                if (!(state is DummySimulative))
                {
                    if (state is ICloth)
                    {
                        environment.Core.RemoveClothActor(state.Name);
                    }
                    else if (state is IRidig)
                    {
                        environment.Core.RemoveRidigActor(state.Name);
                    }
                }
            }
            catch (ArgumentOutOfRangeException)
            {
                //TODO: Error handler (if needed) on Actor not found on a scene
            }
        }

        /// <summary>
        /// Puts simulation element into a scene
        /// </summary>
        /// <param name="enviroment"></param>
        public void CommitToScene(SimulationEnvironment enviroment)
        {           
            state.PutOrUpdate(enviroment);
        }

        #region ISimulatable Members

        public void UpdateState(IList<SimulationCore.Net.DTO.ActorState> actorsState)
        {
            try
            {
                var selfState = state.FindOwnState(actorsState);

                if (state is ICloth)
                {
                    (state as ICloth).UpdateClothState(selfState, ref shape);
                }
                else
                {
                    state.UpdateSimulationState(selfState);
                }
            }
            catch (InvalidOperationException e)
            {
                if (state is DummySimulative)
                {
                    //Supress error
                    LogManager.LogDebug("Supressed exception occured when Updating State of Actor {0}. Exception is {1}",
                                        Name,
                                        e);
                    return;
                }
                throw;
            }
            catch (DataException)
            {
                //TODO: Log abnormal situation
            }
        }

        #endregion

        #region IDrawable Members

        public void DrawActor(Microsoft.DirectX.Direct3D.Device device)
        {
            Matrix transformation;

            if (state is ICloth)
            {
                transformation = Matrix.Identity;
            }
            else
            {
                transformation = state.Transformation;
            }
            IndexedVerticesDrawer.Draw(shape, transformation, device);
        }

        public void Highlight(System.Drawing.Color color)
        {
            //TODO: Modify Material to be marked with given color
        }

        public override string ToString()
        {
            return Name;
        }

        #endregion

        #region IActorPropertiesProvider Members

        public bool Visible
        {
            get
            {
                return shape.Visible;
            }
            set
            {
                shape.Visible = value;
            }
        }

        public Vector3 Position
        {
            get
            {
                //Get position from World Transformation Matrix
                if (null == state)
                {
                    throw new InvalidOperationException(
                        "Position could not be generated. There is no state defied for actor.");
                }

                var position = new Vector3(0, 0, 0);
                position.TransformCoordinate(state.Transformation);

                return position;
            }
        }

        public string Name
        {
            get 
            {
                if (null == state)
                {
                    return "Undefined";
                }
                return state.Name;
            }
        }

        #endregion
    }
}
